package qa.tests;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessageHolder;
import org.aspectj.bridge.MessageHandler;
import org.aspectj.tools.ajc.Main;

/**
 * Plugin that counts the number of asserts in test methods.
 * Currently this mojo only supports JUnit 4 asserts. This means
 * that the test methods need to be annotated with the <code>&#64;org.junit.Test</code> annotation.
 * The plugin only counts the assert made through calls to all static methods of the 
 * <code>org.junit.Assert</code> class.
 * 
 * @goal weave
 * 
 * @phase test-compile
 * 
 * @requiresDependencyResolution test
 */
public class TestTestPluginMojo extends AbstractMojo {

    public static final String PATH_WERE_OUTPUT_IS = "test-test-maven-plugin";

    /**
     * Directory containing the build files.
     * 
     * @parameter expression="${project.build.directory}"
     * @required
     * @readonly
     */
    private File buildDirectory;

    /**
     * Directory containing the source of the test classes.
     * 
     * @parameter expression="${project.build.testSourceDirectory}"
     * @required
     * @readonly
     */
    private File testSourceDirectory;

    /**
     * Directory containing the compiled test classes.
     * 
     * @parameter expression="${project.build.testOutputDirectory}"
     * @required
     * @readonly
     */
    private File testOutputDirectory;

    /**
     * <i>Maven Internal</i>: The Project descriptor.
     * 
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * <i>Maven Internal</i>: List of artifacts for the plugin.
     * 
     * @parameter expression="${plugin.artifacts}"
     * @required
     * @readonly
     */
    private List<Artifact> pluginArtifacts;

    /**
     * Artifact factory.
     * 
     * @component
     */
    private ArtifactFactory factory;

    public void execute() throws MojoExecutionException {
        try {
            writeAssertCounterPropertyFileForAspect();
            String[] args = createArgsForAjc();
            IMessageHolder holder = runAjc(args);
            printMessages(holder);
            addPluginDependenciesToTestClasspath();
        } catch (DependencyResolutionRequiredException e) {
            throw new MojoExecutionException("DependencyResolutionRequired to determine testclasspath", e);
        } catch (IOException e) {
            throw new MojoExecutionException("IOException while writing property file " + StatisticsWriter.PROPERTY_FILENAME, e);
        }
    }

    private String[] createArgsForAjc() throws MojoExecutionException, DependencyResolutionRequiredException {
        String aspectPath = getAspectPath();
        String classpath = getClasspath();
        File sources = createSourcesFile();
        String[] args = createArgs(aspectPath, classpath, sources);
        return args;
    }

    private void writeAssertCounterPropertyFileForAspect() throws IOException {
        String outputPath = new File(buildDirectory, PATH_WERE_OUTPUT_IS).getAbsolutePath();
        File file = new File(testOutputDirectory, StatisticsWriter.PROPERTY_FILENAME);
        FileUtils.writeStringToFile(file, outputPath);
    }

    private void printMessages(IMessageHolder holder) {
        IMessage[] messages = holder.getMessages(null, true);
        for (IMessage iMessage : messages) {
            getLog().info(iMessage.getMessage());
        }
    }

    private IMessageHolder runAjc(String[] args) {
        Main ajc = new Main();
        IMessageHolder holder = new MessageHandler();
        ajc.run(args, holder);
        return holder;
    }

    private String[] createArgs(String aspectPath, String classpath, File sources) {
        String[] args = new String[] { "-cp", classpath, "-source", "1.5", "-verbose", "-showWeaveInfo", "-g", "-aspectpath", aspectPath, "-d",
                testOutputDirectory.getAbsolutePath(), "@" + sources.getAbsolutePath() };
        printArgs(args);
        return args;
    }

    private void printArgs(String[] args) {
        StringBuilder builder = new StringBuilder("ajc command: ");
        for (String arg : args) {
            builder.append(arg).append(" ");
        }
        getLog().info(builder.toString().trim());
    }

    private File createSourcesFile() throws MojoExecutionException {
        Collection<File> files = FileUtils.listFiles(testSourceDirectory, new String[] { "java" }, true);
        List<String> fileNames = new ArrayList<String>();
        for (File file : files) {
            fileNames.add(file.getAbsolutePath());
        }
        File sources = new File(buildDirectory, "sources.lst");
        try {
            FileUtils.writeLines(sources, fileNames);
        } catch (IOException e) {
            throw new MojoExecutionException("unable to create file containing sources that need to be woven", e);
        }
        return sources;
    }

    private String getClasspath() throws DependencyResolutionRequiredException {
        StringBuffer classpathElements = new StringBuffer();
        for (Artifact element : pluginArtifacts) {
            classpathElements.append(element.getFile().getAbsolutePath()).append(":");
        }
        List<String> elements = project.getTestClasspathElements();
        for (String element : elements) {
            classpathElements.append(element).append(":");
        }
        String classpath = classpathElements.toString();
        classpath = classpath.substring(0, classpath.length() - 1);
        return classpath;
    }

    private String getAspectPath() throws MojoExecutionException {
        for (Artifact element : pluginArtifacts) {
            if (element.getArtifactId().equalsIgnoreCase("assert-counter")) {
                return element.getFile().getAbsolutePath();
            }
        }
        throw new MojoExecutionException("Couldn't find 'assert-counter' artifact in plugin dependencies");
    }

    private void addPluginDependenciesToTestClasspath() throws MojoExecutionException {
        Map pluginArtifactMap = ArtifactUtils.artifactMapByVersionlessId(pluginArtifacts);
        Artifact assertCounter = (Artifact) pluginArtifactMap.get("qa.tests:assert-counter");
        if (assertCounter == null) {
            throw new MojoExecutionException("Couldn't find 'assert-counter' artifact in plugin dependencies");
        }

        assertCounter = artifactScopeToTest(assertCounter);
        Set set = new LinkedHashSet();
        if (this.project.getDependencyArtifacts() != null) {
            set.addAll(this.project.getDependencyArtifacts());
        } 
        set.add(assertCounter);
        this.project.setDependencyArtifacts(set);
    }

    private Artifact artifactScopeToTest(Artifact artifact) {
        return factory.createArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
                Artifact.SCOPE_TEST, artifact.getType());
    }

}
